//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "Graphics/Systems/Render.h"
#include "Graphics/Systems/GraphicsEngine.h"
#include "Graphics/Primitives/PointPrimitives.h"
#include "Graphics/Primitives/LinePrimitives.h"
#include "Graphics/Primitives/RectPrimitives.h"


namespace Engine
{
namespace Graphics
{

//-------------------------- Render2D -----------------------------//
/*
=================================================
	DrawLine
=================================================
*/
	void Render2D::DrawLine (const BatchMaterial &material, const line2d &line, const color4u &color) const
	{
		return DrawLines( material, uni_array< const line2d >( &line, 1 ), color );
	}

/*
=================================================
	DrawLines
=================================================
*/
	void Render2D::DrawLines (const BatchMaterial &material, const uni_array< const line2d > &lines, const color4u &color) const
	{
		typedef Primitives::ColoredLine2D		ColoredLine2D;
		typedef Primitives::LineVertex2D		Vertex2D;

		BatchRenderPtr	br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		const vec2 &	aspect	= ESS()->GetGraphicsEngine()->GetViewport()->GetResCorrectAspect();
		
		br->SetMaterial< ColoredLine2D >( material );

		FOR( i, lines )
		{
			br->AddBatch( ColoredLine2D( Vertex2D( lines[i].Begin() * aspect, color ),
										 Vertex2D( lines[i].End() * aspect, color ) ) );
		}
	}

/*
=================================================
	DrawQuad
=================================================
*/
	void Render2D::DrawQuad (const BatchMaterial &material, const vec2 &pos, float side,
							 const color4u &color, bool fill) const
	{
		DrawRect( material, pos, vec2(side), color, fill, true );
	}
	
/*
=================================================
	DrawRect
=================================================
*/
	void Render2D::DrawRect (const BatchMaterial &material, const vec2 &pos, const vec2 &sides,
							 const color4u &color, bool fill, bool resolutionCorrection) const
	{
		BatchRenderPtr	br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		const vec2		aspect	= resolutionCorrection ?
									ESS()->GetGraphicsEngine()->GetViewport()->GetResCorrectAspect() :
									vec2(1.0f);

		if ( fill )
			_DrawFillRect( br, aspect, material, pos, sides, color );
		else
			_DrawLineRect( br, aspect, material, pos, sides, color );
	}
	
/*
=================================================
	DrawRectRotated
=================================================
*/
	void Render2D::DrawRectRotated (const BatchMaterial &material, const vec2 &pos, const vec2 &sides,
									const color4u &color, float angle, bool fill, bool resolutionCorrection) const
	{
		BatchRenderPtr	br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		const vec2		aspect	= resolutionCorrection ?
									ESS()->GetGraphicsEngine()->GetViewport()->GetResCorrectAspect() :
									vec2(1.0f);

		if ( fill )
			_DrawFillRectRotated( br, aspect, material, pos, sides, color, angle );
		else
			_DrawLineRectRotated( br, aspect, material, pos, sides, color, angle );
	}

/*
=================================================
	DrawCircle
=================================================
*/
	void Render2D::DrawCircle (const BatchMaterial &material, const vec2 &pos, float radius,
								uint segments, const color4u &color, bool fill) const
	{
		BatchRenderPtr	br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		const vec2 &	aspect	= ESS()->GetGraphicsEngine()->GetViewport()->GetResCorrectAspect();

		if ( segments < 4 )
		{
			WARNING( "incorrect number of segments!" );
			segments = 6;
		}

		if ( fill )
			_DrawFillCircle( br, aspect, material, pos, radius, segments, color );
		else
			_DrawLineCircle( br, aspect, material, pos, radius, segments, color );
	}
	
/*
=================================================
	DrawArc
=================================================
*/
	void Render2D::DrawArc (const BatchMaterial &material, const vec2 &pos, float radius,
							float startAngle, float angle, uint segments, const color4u &color) const
	{
		BatchRenderPtr	br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		const vec2 &	aspect	= ESS()->GetGraphicsEngine()->GetViewport()->GetResCorrectAspect();

		typedef Primitives::LineStripPoint2D	LineStripPoint2D;
		typedef Primitives::LineVertex2D		Vertex2D;

		br->SetMaterial< LineStripPoint2D >( material );
		
		if ( segments < 2 )
		{
			WARNING( "incorrect number of segments!" );
			segments = 6;
		}

		for (uint i = 0; i < segments; ++i)
		{
			const float	a = angle * float(i) / segments;

			const vec2	p = pos + radius * SinCos( startAngle + a ).yx() * aspect;

			br->AddBatch( LineStripPoint2D( Vertex2D( p, color ) ) );
		}
	}

/*
=================================================
	_DrawLineRect
=================================================
*/
	void Render2D::_DrawLineRect (const BatchRenderPtr &br, const vec2 &aspect, const BatchMaterial &material,
								  const vec2 &pos, const vec2 &sides, const color4u &color)
	{
		typedef Primitives::LineStripPoint2D	LineStripPoint2D;
		typedef Primitives::LineVertex2D		Vertex2D;

		br->SetMaterial< LineStripPoint2D >( material );

		const vec2	size = vec2(sides) * aspect * 0.5f;
		const vec2	points[4] = {
						vec2( -size.x, -size.y ),		vec2( -size.x,  size.y ),
						vec2(  size.x,  size.y ),		vec2(  size.x, -size.y ) };
		uint		index = br->GetCurrentIndex();

		br->AddBatch( LineStripPoint2D( Vertex2D( pos + points[0], color ) ) );		// left  bottom
		br->AddBatch( LineStripPoint2D( Vertex2D( pos + points[1], color ) ) );		// left  top
		br->AddBatch( LineStripPoint2D( Vertex2D( pos + points[2], color ) ) );		// right top
		br->AddBatch( LineStripPoint2D( Vertex2D( pos + points[3], color ) ) );		// right bottom
		br->AddIndex( index );
	}
	
/*
=================================================
	_DrawFillRect
=================================================
*/
	void Render2D::_DrawFillRect (const BatchRenderPtr &br, const vec2 &aspect, const BatchMaterial &material,
								  const vec2 &pos, const vec2 &sides, const color4u &color)
	{
		typedef Primitives::Rect2D	Rect2D;
		
		const vec2	size = vec2(sides) * aspect * 0.5f;

		br->AddBatch( material, Rect2D( rect( pos - size, pos + size ), rect( 0.0f, 0.0f, 1.0f, 1.0f ), color ) );
	}
	
/*
=================================================
	_DrawLineRectRotated
=================================================
*/
	void Render2D::_DrawLineRectRotated (const BatchRenderPtr &br, const vec2 &aspect, const BatchMaterial &material,
											const vec2 &pos, const vec2 &sides, const color4u &color, float angle)
	{
		typedef Primitives::Parallelogram2D		Parall2D;
		typedef Primitives::Rect2D				Rect2D;
		typedef Primitives::LineStripPoint2D	LineStripPoint2D;
		typedef Primitives::LineVertex2D		Vertex2D;

		br->SetMaterial< LineStripPoint2D >( material );

		const vec2	size = vec2(sides) * aspect * 0.5f;
		const uint	index = br->GetCurrentIndex();
		Parall2D	parall = Parall2D( Rect2D( rect( pos - size, pos + size ), rect(), color ) );
		
		parall.Rotate( SinCos( angle ) );

		br->AddBatch( LineStripPoint2D( Vertex2D( parall.leftBottom.position,	color ) ) );		// left  bottom
		br->AddBatch( LineStripPoint2D( Vertex2D( parall.leftTop.position,		color ) ) );		// left  top
		br->AddBatch( LineStripPoint2D( Vertex2D( parall.rightTop.position,		color ) ) );		// right top
		br->AddBatch( LineStripPoint2D( Vertex2D( parall.rightBottom.position,	color ) ) );		// right bottom
		br->AddIndex( index );
	}
	
/*
=================================================
	_DrawFillRectRotated
=================================================
*/
	void Render2D::_DrawFillRectRotated (const BatchRenderPtr &br, const vec2 &aspect, const BatchMaterial &material,
											const vec2 &pos, const vec2 &sides, const color4u &color, float angle)
	{
		typedef Primitives::Parallelogram2D		Parall2D;
		typedef Primitives::Rect2D				Rect2D;
		
		const vec2	size = vec2(sides) * aspect * 0.5f;
		
		Parall2D	parall = Parall2D( Rect2D( rect( pos - size, pos + size ), rect( 0.0f, 0.0f, 1.0f, 1.0f ), color ) );

		parall.Rotate( SinCos( angle ) );
		br->AddBatch( material, parall );
	}

/*
=================================================
	_DrawLineCircle
=================================================
*/
	void Render2D::_DrawLineCircle (const BatchRenderPtr &br, const vec2 &aspect, const BatchMaterial &material,
									const vec2 &pos, float radius, uint segments, const color4u &color)
	{
		typedef Primitives::LineStripPoint2D	LineStripPoint2D;
		typedef Primitives::LineVertex2D		Vertex2D;

		br->SetMaterial< LineStripPoint2D >( material );
		uint index = br->GetCurrentIndex();

		for (uint i = 0; i < segments; ++i)
		{
			const vec2	p = pos + radius * SinCos( 2.0f * Pi<float>() * float(i) / segments ).yx() * aspect;

			br->AddBatch( LineStripPoint2D( Vertex2D( p, color ) ) );
		}
		
		br->AddIndex( index );
	}
	
/*
=================================================
	_DrawFillCircle
=================================================
*/
	void Render2D::_DrawFillCircle (const BatchRenderPtr &br, const vec2 &aspect, const BatchMaterial &material,
									const vec2 &pos, float radius, uint segments, const color4u &color)
	{
		typedef Primitives::TrianglePoint2D		TrianglePoint2D;
		typedef Primitives::Vertex2D			Vertex2D;
		
		br->SetMaterial< TrianglePoint2D >( material );
		const uint index = br->GetCurrentIndex();
		
		// center
		br->AddVertex( Vertex2D( pos, vec2(0.5f), color ) );


		// contur
		for (uint i = 0; i < segments; ++i)
		{
			const vec2	p  = radius * SinCos( 2.0f * Pi<float>() * float(i) / segments ).yx() * aspect;
			const vec2	tc = p / radius + 1.0f;

			br->AddVertex( Vertex2D( pos + p, tc, color ) );

			if ( i != 0 )
			{
				br->AddIndex( index );
				br->AddIndex( index + i );
				br->AddIndex( index + i + 1 );
			}
		}
		
		br->AddIndex( index );
		br->AddIndex( index + 1 );
		br->AddIndex( index + segments );
	}
	
/*
=================================================
	DrawQuadTransformed
=================================================
*
	void Render2D::DrawQuadTransformed (const BatchMaterial &material, const rect &region,
										const color4u &color, const mat2 &transform) const
	{
		BatchRenderPtr	br = ESS()->GetGraphicsEngine()->GetBatchRender();

	}

/*
=================================================
	DrawPolyLine
----
	step - size of one element in line
=================================================
*
	void Render2D::DrawPolyLine (const BatchMaterial &material, const Primitives::PolyLine2D &polyLine,
								 const color4u &color, float step) const
	{
		/*typedef Primitives::LineStripPoint2D		LineStrip2D;
		typedef LineStrip2D::vertex_t				Vert2D;
		typedef Primitives::PolyLine2D::EPrimitive	EPrimitive;
		typedef TCircle< float >					circle_t;

		Primitives::PolyLine2D::Reader	reader	= polyLine.GetReader();
		BatchRenderPtr					br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		
		br->AddBatch( material, LineStrip2D( Vert2D( reader.GetStartPoint(), color ) ) );

		do
		{
			switch ( reader.GetType() )
			{
				case EPrimitive::LINE :
				{
					vec2	p, n;
					reader.GetLine( p, n );
					br->AddBatch( LineStrip2D( Vert2D( p, color ) ) );
					break;
				}

				case EPrimitive::SPLINE :
				{
					vec2	p0, p1, p2, p3;
					
					reader.GetSpline( p0, p1, p2, p3 );

					const float	da = 1.0f / Round<int>( p2.Distance( p1 ) / step );
			
					for (float a = 0.0f; a <= 1.0f; a += da)
					{
						const vec2	p = UXMathExt::TSpline::CatmullRom( p0, p1, p2, p3, a );

						br->AddBatch( LineStrip2D( Vert2D( p, color ) ) );
					}
					break;
				}
					
				case EPrimitive::ARC :
				{
					circle_t	c;
					vec2		angles;
					reader.GetArc( c.Center(), c.Radius(), angles );

					const float	angle_step	= c.AngleFromArcLength( step );
					const float	da			= CopySign( angles.y - angles.x, angle_step );
					const uint	count		= Max( Round<uint>( Abs( angles.y - angles.x ) / angle_step ), 1u );

					for (uint i = 0; i <= count; ++i)
					{
						const float	a = i * da + angles.x;
						const vec2	p = c.GetPoint( a );

						br->AddBatch( LineStrip2D( Vert2D( p, color ) ) );
					}
					break;
				}
			}
		}
		while ( reader.NextChunk() );* /
	}
	
/*
=================================================
	DrawPolyLine
----
	step - size of one element in line
	tcStep - size of one texcoord element in line
=================================================
*
	void Render2D::DrawPolyLine (const BatchMaterial &material, const Primitives::PolyLine2D &polyLine,
								 const color4u &color, const rect &texcoord, float width, float step, float tcStep) const
	{/*
#if 1
		typedef Primitives::TriangleStripPoint2D	TriStrip2D;
#else
		typedef Primitives::Point< Primitives::Vertex2D, gl_primitive::LINE_STRIP >		TriStrip2D;
#endif
		typedef TriStrip2D::vertex_t				Vert2D;
		typedef Primitives::PolyLine2D::EPrimitive	EPrimitive;
		typedef TCircle< float >					circle_t;

		Primitives::PolyLine2D::Reader	reader	= polyLine.GetReader();
		BatchRenderPtr					br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		const vec2 &					aspect	= ESS()->GetGraphicsEngine()->GetViewport()->GetResCorrectAspect();
		
		br->SetMaterial< TriStrip2D >( material );

		const uint16	tri_strip_idx[4] = { 0, 1, 2, 3 };
		
		vec2	prev = reader.GetStartPoint();
		vec2	norm;
		bool	tc_side = false;
		uint	tc_count = Max( Round<uint>( tcStep / step ), 1u );
		uint	j = 0;
		bool	start = true;

		do
		{
			switch ( reader.GetType() )
			{
				case EPrimitive::LINE :
				{
					line2d	line;
					vec2	tmp_n;
					reader.GetLine( line, tmp_n );

					const float	da = 1.0f / Max( Round<int>( line.Length() / step ), 1 );

					for (float a = (start ? da : 0.0f); a <= 1.0f+da; a += da)
					{
						const vec2		p		= Mix( line.Begin(), line.End(), a );
						//const line2d	l		= line2d( prev, p );
						const vec2		n		= tmp_n; //l.Normal();
						const vec2		delta	= ( n + norm ).Normalized() * aspect * width;
						
						br->AddIndicesWithOffset( tri_strip_idx );

						br->AddVertex( Vert2D( prev + delta, vec2( texcoord[tc_side*2], texcoord[1] ), color ) );
						br->AddVertex( Vert2D( prev - delta, vec2( texcoord[tc_side*2], texcoord[3] ), color ) );
						
						if ( ++j == tc_count )
						{
							j = 0;
							tc_side	= not tc_side;
						}

						prev = p;
						norm = n;
					}
					break;
				}

				case EPrimitive::SPLINE :
				{
					vec2	p0, p1, p2, p3;

					reader.GetSpline( p0, p1, p2, p3 );
					
					const float	da = 1.0f / Max( Round<int>( p2.Distance( p1 ) / step ), 1 );

					for (float a = (start ? da : 0.0f); a <= 1.0f+da; a += da)
					{
						const vec2		p		= UXMathExt::TSpline::CatmullRom( p0, p1, p2, p3, a );
						const line2d	l		= line2d( prev, p );
						const vec2		n		= l.Normal();
						const vec2		delta	= ( n + norm ).Normalized() * aspect * width;

						br->AddIndicesWithOffset( tri_strip_idx );

						br->AddVertex( Vert2D( prev + delta, vec2( texcoord[tc_side*2], texcoord[1] ), color ) );
						br->AddVertex( Vert2D( prev - delta, vec2( texcoord[tc_side*2], texcoord[3] ), color ) );

						if ( ++j == tc_count )
						{
							j = 0;
							tc_side	= not tc_side;
						}

						prev = p;
						norm = n;
					}
					break;
				}
					
				case EPrimitive::ARC :
				{
					circle_t	c;
					vec2		angles;
					reader.GetArc( c.Center(), c.Radius(), angles );

					const float	angle_step	= c.AngleFromArcLength( step );
					const float	da			= CopySign( angles.y - angles.x, angle_step );
					const uint	count		= Max( Round<uint>( Abs( angles.y - angles.x ) / angle_step ), 1u );

					for (uint i = 1; i < count; ++i)
					{
						const float		a		= i * da + angles.x;
						const vec2		p		= c.GetPoint( a );
						const line2d	l		= line2d( prev, p );
						const vec2		n		= l.Normal();
						const vec2		delta	= ( n + norm ).Normalized() * aspect * width;
						
						br->AddIndicesWithOffset( tri_strip_idx );

						br->AddVertex( Vert2D( prev + delta, vec2( texcoord[tc_side*2], texcoord[1] ), color ) );
						br->AddVertex( Vert2D( prev - delta, vec2( texcoord[tc_side*2], texcoord[3] ), color ) );

						if ( ++j == tc_count )
						{
							j = 0;
							tc_side	= not tc_side;
						}

						prev = p;
						norm = n;
					}
					break;
				}
			}
			start = false;
		}
		while ( reader.NextChunk() );

		const vec2	delta = norm * aspect * width;

		br->AddVertex( Vert2D( prev + delta, vec2( texcoord[tc_side*2], texcoord[1] ), color ) );
		br->AddVertex( Vert2D( prev - delta, vec2( texcoord[tc_side*2], texcoord[3] ), color ) );* /
	}

/*
=================================================
	DrawSpline
----
	step - size of one element in line
=================================================
*/
	void Render2D::DrawSpline (const BatchMaterial &material, const uni_array< const vec2 > &points,
								const color4u &color, float step) const
	{
		/*typedef Primitives::LineStripPoint2D		LineStrip2D;
		typedef LineStrip2D::vertex_t				Vert2D;

		BatchRenderPtr	br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		
		br->SetMaterial< LineStrip2D >( material );

		for (usize i = 0; i+1 < points.Count(); ++i)
		{
			vec2	p0 = ( i == 0 ? points[0] : points[i-1] ),
					p1 = points[i],
					p2 = points[i+1],
					p3 = ( i+2 > points.LastIndex() ? points.Back() : points[i+2] );
			
			const float	da = 1.0f / Round<int>( p2.Distance( p1 ) / step );
			
			for (float a = 0.0f; a <= 1.0f; a += da)
			{
				const vec2	p = UXMathExt::TSpline::CatmullRom( p0, p1, p2, p3, a );

				br->AddBatch( LineStrip2D( Vert2D( p, color ) ) );
			}
		}*/
	}

/*
=================================================
	DrawSpline
----
	step - size of one element in line
	tcStep - size of one texcoord element in line
=================================================
*/
	void Render2D::DrawSpline (const BatchMaterial &material, const uni_array< const vec2 > &points,
								const color4u &color, const rect &texcoord, float width, float step, float tcStep) const
	{
		/*typedef Primitives::TriangleStripPoint2D	TriStrip2D;
		//typedef Primitives::Point< Primitives::Vertex2D, gl_primitive::LINE_STRIP >		TriStrip2D;
		typedef TriStrip2D::vertex_t				Vert2D;


		BatchRenderPtr	br		= ESS()->GetGraphicsEngine()->GetBatchRender();
		const vec2 &	aspect	= ESS()->GetGraphicsEngine()->GetViewport()->GetResCorrectAspect();

		br->SetMaterial< TriStrip2D >( material );

		const uint16	tri_strip_idx[4] = { 0, 1, 2, 3 };

		vec2	prev;
		vec2	norm;
		bool	tc_side = false;
		uint	tc_count = Max( Round<uint>( tcStep / step ), 1u );
		uint	j = 0;

		for (usize i = 0; i+1 < points.Count(); ++i)
		{
			vec2	p0 = ( i == 0 ? points[0] : points[i-1] ),
					p1 = points[i],
					p2 = points[i+1],
					p3 = ( i+2 > points.LastIndex() ? points.Back() : points[i+2] );
			
			const float	da = 1.0f / Round<int>( p2.Distance( p1 ) / step );

			float	a  = da;

			if ( i == 0 )
				prev = UXMathExt::TSpline::CatmullRom( p0, p1, p2, p3, 0.0f );
			else
				a = 0.0f;

			for (; a <= 1.0f; a += da)
			{
				const vec2		p		= UXMathExt::TSpline::CatmullRom( p0, p1, p2, p3, a );
				const line2d	l		= line2d( prev, p );
				const vec2		n		= l.Normal();
				const vec2		delta	= ( n + norm ).Normalized() * aspect * width;

				br->AddIndicesWithOffset( tri_strip_idx );

				br->AddVertex( Vert2D( prev + delta, vec2( texcoord[tc_side*2], texcoord[1] ), color ) );
				br->AddVertex( Vert2D( prev - delta, vec2( texcoord[tc_side*2], texcoord[3] ), color ) );

				if ( ++j == tc_count )
				{
					j = 0;
					tc_side	= not tc_side;
				}

				prev = p;
				norm = n;
			}
		}
		
		const vec2	delta = norm * aspect * width;

		br->AddVertex( Vert2D( prev + delta, vec2( texcoord[tc_side*2], texcoord[1] ), color ) );
		br->AddVertex( Vert2D( prev - delta, vec2( texcoord[tc_side*2], texcoord[3] ), color ) );*/
	}


	
//-------------------------- Render3D -----------------------------//


}	// Graphics
}	// Engine